﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Configuration;
using System.IO;
using System.Xml;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using System.Reflection;

namespace teach
{
    // The possible updates made to the form by the controller
    public enum UIUPDATES { USER_BUTTON, TITLE, PANELSWITCH, LABELVIDEO, SETQUESTION, STATS, STATUS, CINEMA, DOUBLE };

    /// <summary>
    /// The teach app controller
    /// </summary>
    public class TeachController
    {
        public TeachForm MainForm { get; set; }
        public FormUsers UsersForm { get; set; }

        private User m_currentUser;
        private Question m_currentQuestion = null;
        private List<Question> m_wrongQuestions = new List<Question>();
        private Dictionary<string, User> m_userDictionary;
        private Dictionary<string, QuestionType> m_questionTypeDictionary;
        private List<string> userList = new List<string>();
        private int m_currentUserIndex = 0;

        private CultureInfo info = new CultureInfo("en-ca");

        private bool finalScored = false;
        private int questions = 0;
        private int goodAnswers = 0;
        private int streak = 0;
        private int numVideos = 0;
        private bool DoubleNothinHappened = false;
        private bool DoubleNothinGoinOn = false;
        private bool DoubleRisk = false;
        private Random rnd = new Random();

        public static Random RND = new Random();

        /// <summary>
        /// constructor
        /// </summary>
        public TeachController()
        {
            TeachController.Log("Prof "+ Assembly.GetEntryAssembly().GetName().Version + " started on " + DateTime.Now.ToLongDateString() + " - " + DateTime.Now.ToShortTimeString());
            Constants.InitializeFolders();
            
            LoadQuestionTypes(Constants.FILE_QUESTIONS_TYPE);
            LoadUsers(Constants.FILE_USERS);

            UsersForm = new FormUsers(m_userDictionary);
            UsersForm.UserSelected += UsersForm_UserSelected;
            
            MainForm = new TeachForm();
            MainForm.QuestionAnswered += new TeachForm.QuestionAnsweredHandler(MainForm_QuestionAnswered);
            MainForm.NextQuestionAsked += new EventHandler(MainForm_NextQuestionAsked);
            MainForm.Timeout += new EventHandler(MainForm_Timeout);
            MainForm.UserChanged += new EventHandler(MainForm_UserChanged);
            MainForm.MainFormClosing += new EventHandler(MainForm_MainFormClosing);

        }

        private void UsersForm_UserSelected(string user)
        {
            MainForm.Show();

            if (m_userDictionary.Count > 1)
            {
                m_wrongQuestions.Clear();
                m_currentUserIndex++;
                if (m_currentUserIndex >= userList.Count)
                {
                    m_currentUserIndex = 0;
                }
                m_currentUser = m_userDictionary[user];

                UpdateUI(UIUPDATES.USER_BUTTON, m_currentUser.UserImage);
                UpdateUI(UIUPDATES.TITLE, "Bienvenue " + m_currentUser.Name);
                TeachController.Log("The user is " + m_currentUser.Name);
                questions = 0;
                goodAnswers = 0;
                streak = 0;
                DoubleNothinHappened = false;
                numVideos = 0;
                UpdateUI(UIUPDATES.STATS, new object[] { goodAnswers, questions, numVideos, streak });
                GetNextQuestion();
            }


            UsersForm.Hide();
        }

        private void MainForm_MainFormClosing(object sender, EventArgs e)
        {
            if (finalScored == false)
            {
                finalScored = true;
                TeachController.Log("Prof ended on " + DateTime.Now.ToLongDateString() + " - " + DateTime.Now.ToShortTimeString());
                Log("FINAL SCORE FOR " + m_currentUser.Name.ToUpper() + " : " + goodAnswers + " / " + questions
                    + " (" + numVideos + " videos)");
                Log("");
            }

            Application.Exit();
        }

        public static void Log(string line)
        {
            StreamWriter writer = new StreamWriter(prof.Properties.Settings.Default.LogPath,true);
            writer.WriteLine(line);
            writer.Close();
        }

        public static void LogSuggestedAnswer(string line)
        {
            StreamWriter writer = new StreamWriter(@"C:\Users\Christian Touzel\Dropbox\Write\ProfSuggestedAnswers.txt", true);
            writer.WriteLine(line);
            writer.Close();
        }

        /// <summary>
        /// Log an error
        /// </summary>
        public static void LogError(string source, Exception ex)
        {
            Log("");
            Log("========= ERROR ==========");
            Log("SOURCE: " + source);
            Log(ex.Message);
            Log(ex.StackTrace);
            Log("========= ERROR ==========");
            Log("");
        }

        public static void LogError(string source, Dictionary<string,string> properties, Exception ex)
        {
            Log("");
            Log("========= ERROR ==========");
            Log("SOURCE: " + source);
            Log(ex.Message);
            Log(ex.StackTrace);
            Log("PROPERTIES:");
            foreach (string str in properties.Keys)
            {
                Log("  " + str + " = " + properties[str]);
            }
            Log("========= ERROR ==========");
            Log("");
        }

        /// <summary>
        /// Change the current user
        /// </summary>
        private void MainForm_UserChanged(object sender, EventArgs e)
        {
            if (m_userDictionary.Count > 1)
            {
                m_wrongQuestions.Clear();
                m_currentUserIndex++;
                if (m_currentUserIndex >= userList.Count)
                {
                    m_currentUserIndex = 0;
                }
                m_currentUser = m_userDictionary[userList[m_currentUserIndex]];

                UpdateUI(UIUPDATES.USER_BUTTON, m_currentUser.UserImage);
                UpdateUI(UIUPDATES.TITLE, "Bienvenue " + m_currentUser.Name);
                TeachController.Log("The user is " + m_currentUser.Name);
                questions = 0;
                goodAnswers = 0;
                streak = 0;
                DoubleNothinHappened = false;
                numVideos = 0;
                UpdateUI(UIUPDATES.STATS, new object[] { goodAnswers, questions, numVideos, streak });
                GetNextQuestion();
            }
        }

        /// <summary>
        /// Manage a question timeout
        /// </summary>
        private void MainForm_Timeout(object sender, EventArgs e)
        {
            //streak = 0;
            //UpdateUI(UIUPDATES.STATS, new object[] { goodAnswers, questions, numVideos, streak });
        }

        /// <summary>
        /// Manage a next question asked
        /// </summary>
        private void MainForm_NextQuestionAsked(object sender, EventArgs e)
        {
            GetNextQuestion();
            if (DoubleNothinGoinOn == false)
            {
                UpdateUI(UIUPDATES.TITLE, m_currentQuestion.Message);
            }
        }

        /// <summary>
        /// Manage the answering of the question
        /// </summary>
        private void MainForm_QuestionAnswered(object answer)
        {
            if(DoubleNothinGoinOn == true)
            {
                // Manage a yes or no
                // if no... then new question as if nothing happened
                // if yes... crank the challenge
                if (answer.ToString() == "NON")
                {
                    DoubleNothinGoinOn = false;
                    GetNextQuestion();
                    UpdateUI(UIUPDATES.TITLE, m_currentQuestion.Message);
                }
                else
                {
                    DoubleNothinGoinOn = false;
                    DoubleRisk = true;
                    TeachController.Log("--- ALL OR NOTHING! ---");

                    GetNextQuestion();
                    UpdateUI(UIUPDATES.TITLE, m_currentQuestion.Message);
                }
            }
            else
            {
                if (m_currentQuestion.CheckAnswer(answer))
                {
                    if (DoubleRisk)
                    {
                        DoubleRisk = false;
                        TeachController.Log("--- ONE VIDEO BONUS!");
                        numVideos += 1;
                    }

                    goodAnswers++;
                    streak++;
                    UpdateUI(UIUPDATES.STATUS, new object[] { true, GetStatusImage(true) });
                    if (m_currentUser.Video)
                    {
                        if (streak % 10 == 0)
                        {
                            string url = GetVideoURL(true);
                            TeachController.Log(m_currentUser.Name + " has a long video : " + url);
                            UpdateUI(UIUPDATES.CINEMA, url);
                            numVideos++;
                        }
                        else if (streak % 5 == 0)
                        {
                            string url = GetVideoURL(false);
                            TeachController.Log(m_currentUser.Name + " has a short video : " + url);
                            UpdateUI(UIUPDATES.CINEMA, url);
                            numVideos++;
                        }
                    }
                }
                else
                {
                    streak = 0;
                    UpdateUI(UIUPDATES.STATUS, new object[] { false, GetStatusImage(false) });
                    if (m_currentQuestion.IsRevisable())
                    {
                        m_wrongQuestions.Add(m_currentQuestion);
                    }
                    if (DoubleRisk)
                    {
                        DoubleRisk = false;
                        numVideos -= 1;
                        TeachController.Log("--- ONE VIDEO LOST...");
                    }
                }
                DoubleNothinGoinOn = false;
                UpdateUI(UIUPDATES.STATS, new object[] { goodAnswers, questions, numVideos, streak });
                if (numVideos > -1)
                {
                    UpdateUI(UIUPDATES.LABELVIDEO, new object[] { numVideos });
                }
            }

        }

        /// <summary>
        /// Get a video for the reward
        /// </summary>
        /// <param name="longVideo"></param>
        /// <returns></returns>
        private string GetVideoURL(bool longVideo)
        {
            try
            {
                List<string> historicLines = new List<string>();
                List<DateTime> historicDates = new List<DateTime>();

                string videoPath = Constants.FOLDER_REWARDS_SHORT;

                if (longVideo)
                {
                    videoPath = Constants.FOLDER_REWARDS_LONG;
                }
                string historyPath = Path.Combine(videoPath, "history.txt");

                DirectoryInfo infodir = new DirectoryInfo(videoPath);
                FileInfo[] files = infodir.GetFiles("*.wmv");
                Random rnd = new Random();

                // load history file
                if (File.Exists(historyPath))
                {
                    StreamReader reader = new StreamReader(historyPath);

                    string line = reader.ReadLine();

                    while (line != null)
                    {
                        string datestr = line.Substring(0, 10);
                        DateTime stamp = DateTime.Parse(datestr, info);

                        TimeSpan span = DateTime.Now - stamp;
                        if (span.TotalDays < Constants.HISTORY_MAX_DAYS)
                        {
                            historicLines.Add(line.Substring(11));
                            historicDates.Add(stamp);
                        }
                        line = reader.ReadLine();
                    }
                    reader.Close();
                }

                // pick a file
                int index = rnd.Next(files.Length);
                while (historicLines.Contains(files[index].Name))
                {
                    index = rnd.Next(files.Length);
                }

                historicLines.Add(files[index].Name);
                historicDates.Add(DateTime.Now);
                StreamWriter writer = new StreamWriter(historyPath, false);
                int count = historicLines.Count;
                for (int i = 0; i < count; i++)
                {
                    writer.WriteLine(historicDates[i].ToString("yyyy-MM-dd") + " " + historicLines[i]);
                }
                writer.Close();
                return files[index].FullName;
            }
            catch (Exception ex)
            {
                LogError("GetVideoURL(" + longVideo + ")", ex);
                throw ex;
            }
        }

        /// <summary>
        /// Get an image depending on the result
        /// </summary>
        private Image GetStatusImage(bool success)
        {
            Image retour = null;
            switch (success)
            {
                case true:
                    DirectoryInfo info = new DirectoryInfo(Constants.FOLDER_GOOD_ANSWERS);
                    FileInfo[] goodImgs = info.GetFiles();
                    int idx = rnd.Next(goodImgs.Length);
                    retour = new Bitmap(goodImgs[idx].FullName);
                    break;
                case false:
                    DirectoryInfo badinfo = new DirectoryInfo(Constants.FOLDER_BAD_ANSWERS);                  
                    FileInfo[] badImgs = badinfo.GetFiles();
                    int bidx = rnd.Next(badImgs.Length);
                    retour = new Bitmap(badImgs[bidx].FullName);
                    break;
            }
            return retour;
        }

        /// <summary>
        /// Update the UI
        /// </summary>
        private void UpdateUI(UIUPDATES update, object data)
        {
            MainForm.Invoke(MainForm.myUpdateUI, new object[] { update, new object[] { data } });
        }
        private void UpdateUI(UIUPDATES update, object[] data)
        {
            MainForm.Invoke(MainForm.myUpdateUI, new object[] { update, data });
        }

        /// <summary>
        /// Load the questions types
        /// </summary>
        /// <param name="path"></param>
        private void LoadQuestionTypes(string path)
        {
            m_questionTypeDictionary = new Dictionary<string, QuestionType>();
            XmlDocument docTypes = new XmlDocument();
            docTypes.Load(path);
            XmlNodeList nodeListTypes = docTypes.DocumentElement.SelectNodes("type");

            foreach (XmlNode nodeType in nodeListTypes)
            {
                QuestionType type = new QuestionType();
                type.Name = nodeType.SelectSingleNode("Name").InnerText;
                type.UI = nodeType.SelectSingleNode("UI").InnerText;
                if (nodeType.SelectSingleNode("File").InnerText != "NA")
                {
                    type.LoadProperties(Path.Combine(Constants.FOLDER_QUESTIONS, nodeType.SelectSingleNode("File").InnerText));
                }
                m_questionTypeDictionary.Add(type.Name, type);
            }
        }

        /// <summary>
        /// Load the users preferences
        /// </summary>
        /// <param name="path"></param>
        private Dictionary<string, Preference> LoadUserPreferences(string path)
        {
            try
            {
                Dictionary<string, Preference> prefs = new Dictionary<string, Preference>();
                XmlDocument docPrefs = new XmlDocument();
                docPrefs.Load(path);
                XmlNodeList nodeListPrefs = docPrefs.DocumentElement.SelectNodes("preference");
                foreach (XmlNode nodePref in nodeListPrefs)
                {
                    Preference pref = new Preference();
                    pref.Type = nodePref.SelectSingleNode("type").InnerText;
                    pref.Enabled = bool.Parse(nodePref.SelectSingleNode("enabled").InnerText);
                    pref.Weight = int.Parse(nodePref.SelectSingleNode("weight").InnerText);
                    if (nodePref.SelectSingleNode("min") != null)
                    {
                        pref.Min = int.Parse(nodePref.SelectSingleNode("min").InnerText);
                    }
                    if (nodePref.SelectSingleNode("max") != null)
                    {
                        pref.Max = int.Parse(nodePref.SelectSingleNode("max").InnerText);
                    }
                    pref.Timeout = int.Parse(nodePref.SelectSingleNode("timeout").InnerText);
                    prefs.Add(pref.Type, pref);
                }
                return prefs;
            }
            catch (Exception ex)
            {
                LogError("LoadUserPreferences(" + path + ")", ex);
                throw ex;
            }
        }

        /// <summary>
        /// Load the users from the users file
        /// </summary>
        /// <param name="path"></param>
        private void LoadUsers(string path)
        {
            try
            {
                m_userDictionary = new Dictionary<string, User>();
                XmlDocument docUsers = new XmlDocument();
                docUsers.Load(path);
                XmlNodeList nodeListUsers = docUsers.DocumentElement.SelectNodes("user");
                foreach (XmlNode nodeUser in nodeListUsers)
                {
                    User user = new User();
                    user.ID = nodeUser.SelectSingleNode("id").InnerText;
                    user.Name = nodeUser.SelectSingleNode("name").InnerText;
                    user.Video = bool.Parse(nodeUser.SelectSingleNode("video").InnerText);
                    try
                    {
                        user.UserImage = Bitmap.FromFile(Path.Combine(Constants.FOLDER_USERS_PIC, nodeUser.SelectSingleNode("pic").InnerText));
                    }
                    catch
                    {
                        user.UserImage = null;
                    }

                    // load user preferences
                    user.Preferences = LoadUserPreferences(Path.Combine(Constants.FOLDER_QUESTIONS, user.ID.ToLower() + ".xml"));
                    user.RandomList = new List<QuestionType>();
                    foreach (Preference pref in user.Preferences.Values)
                    {
                        if (pref.Enabled)
                        {
                            for (int j = 0; j < pref.Weight; j++)
                            {
                                user.RandomList.Add(m_questionTypeDictionary[pref.Type]);
                            }
                        }
                    }

                    if (m_currentUser == null)
                    {
                        m_currentUser = user;
                    }

                    m_userDictionary.Add(user.ID, user);
                    userList.Add(user.ID);
                }
                if (m_userDictionary.Count > 0)
                {
                    //UpdateUI(UIUPDATES.USER_BUTTON, m_currentUser.UserImage);
                    //UpdateUI(UIUPDATES.TITLE, "Bienvenue " + m_currentUser.Name);
                }
            }
            catch (Exception ex)
            {
                LogError("LoadUsers("+path+")", ex);
                throw ex;
            }
        }

        /// <summary>
        /// Get the next question
        /// </summary>
        private void GetNextQuestion()
        {
            try
            {
                if (DoubleNothinHappened == false)
                {

                    // Calculate if it should pop
                    if (numVideos > 0 && ((streak+1) % 10 == 0 ||(streak+1) % 5 == 0))        // cannot pop when the user has 0 video
                    {
                        Random rnd = new Random();
                        if (numVideos > 5)
                        {
                            if (rnd.Next(100) < 30) // 30% chance
                            {
                                DoubleNothinGoinOn = true;
                                DoubleNothinHappened = true;
                            }
                        }
                        else if (numVideos == 5)
                        {
                            if (rnd.Next(100) < 25) // 25% chance
                            {
                                DoubleNothinGoinOn = true;
                                DoubleNothinHappened = true;
                            }
                        }
                        else if (numVideos == 4)
                        {
                            if (rnd.Next(100) < 20) // 20% chance
                            {
                                DoubleNothinGoinOn = true;
                                DoubleNothinHappened = true;
                            }
                        }
                        else if (numVideos == 3)
                        {
                            if (rnd.Next(100) < 15) // 15% chance
                            {
                                DoubleNothinGoinOn = true;
                                DoubleNothinHappened = true;
                            }
                        }
                        else if (numVideos == 2)
                        {
                            if (rnd.Next(100) < 10) // 10% chance
                            {
                                DoubleNothinGoinOn = true;
                                DoubleNothinHappened = true;
                            }
                        }
                        else if (numVideos == 1)
                        {
                            if (rnd.Next(100) < 5) // 5% chance
                            {
                                DoubleNothinGoinOn = true;
                                DoubleNothinHappened = true;
                            }
                        }

                        
                        // Find the Question
                        // Set the Variables --> DoubleNothinGoinOn = true;
                        // Set the Variables --> DoubleNothinHappened = true;
                    }
                }

                if (DoubleNothinGoinOn)
                {
                    // Pop the question and ask
                    UpdateUI(UIUPDATES.TITLE, "Prendrez-vous le risque?");
                    UpdateUI(UIUPDATES.PANELSWITCH, "DOUBLE");
                }
                else
                {
                    int num = TeachController.RND.Next(30);
                    if (m_wrongQuestions.Count > 2 && num > 28)
                    {
                        m_currentQuestion = m_wrongQuestions[TeachController.RND.Next(m_wrongQuestions.Count)];
                    }
                    else if (m_wrongQuestions.Count > 0 && num == 8)
                    {
                        m_currentQuestion = m_wrongQuestions[TeachController.RND.Next(m_wrongQuestions.Count)];
                    }
                    else
                    {
                        Question newQuestion = GetRandomType().GetNewQuestion(m_currentUser.Name);
                        newQuestion.UI = m_questionTypeDictionary[newQuestion.QuestionType].UI;
                        newQuestion.GenerateValues(m_currentUser.Preferences[newQuestion.QuestionType]);
                        m_currentQuestion = newQuestion;
                    }

                    UpdateUI(UIUPDATES.PANELSWITCH, m_currentQuestion.UI);
                    UpdateUI(UIUPDATES.SETQUESTION, m_currentQuestion);
                    questions++;
                }


            }
            catch (Exception ex)
            {
                Log("m_currentQuestion.UI = " + m_currentQuestion.UI.ToString());
                Log("m_currentQuestion.QuestionType = " + m_currentQuestion.QuestionType.ToString());
                LogError("GetNextQuestion()", ex);
                throw ex;
            }
        }

        /// <summary>
        /// Get a random question type
        /// </summary>
        /// <returns></returns>
        private QuestionType GetRandomType()
        {
            int signrandom = rnd.Next(m_currentUser.RandomList.Count);
            return m_currentUser.RandomList[signrandom];
        }
    }
}
